package com.mathdroid.model.abilities;

import java.io.Serializable;
import java.util.HashMap;

import com.mathdroid.exceptionhandling.DroidMathException;
import com.mathdroid.exceptionhandling.ExceptionHandler;
import com.mathdroid.model.Model;
import com.mathdroid.model.enums.EDmgType;
import com.mathdroid.model.stats.ACharacterStats;
import com.mathdroid.model.stats.StandardDamage;
import com.mathdroid.model.talents.Talent;

/**
 * This class describes an player executed ability, regardless if it is a spell,
 * heal or physical attack.
 */
public abstract class Ability implements Serializable, Cloneable {
	
	/**
	 * Generated UID.
	 */
	private static final long serialVersionUID = -7354817071098619624L;

	/**
	 * The name of the ability.
	 */
	private String name = null;
	
	/**
	 * The description of the ability.
	 */
	private String description = null;
	
	/**
	 * The icon name of the ability.
	 */
	private String iconName = null;
	
	/**
	 * The standard damage lookup table.
	 */
	private final StandardDamage stdDmg = new StandardDamage();
	
	/**
	 * If the ability is enabled or disabled (set by the user).
	 * The default is enabled.
	 */
	private boolean enabled = true;
	
	/**
	 * If the ability is used by the optimizer. This is set by the module
	 * developer.
	 */
	private boolean usedForOptimization = true;
	
	/**
	 * The type of the direct damage.
	 */
	private EDmgType directDamageType = null;

	/**
	 * The type of the periodic damage.
	 */
	private EDmgType periodicDamageType = null;
	
	/**
	 * The duration of the periodic damage aspect.
	 */
	private double durationMs = 0d;
	
	/**
	 * The cool-down time of the ability.
	 */
	private double cooldownMs = 0d;
	
	/**
	 * The required talent.
	 */
	private String requiredTalent = null;
	
	/**
	 * If this ability is affected by the global cooldown.
	 */
	private boolean affectedByGCD = true;
	
	/**
	 * If this ability is affects the global cooldown.
	 */
	private boolean affectsGCD = true;
	
	/**
	 * The cast/channel/activation time in milliseconds.
	 */
	private double castTimeMs = 0d;
	
	/**
	 * The cost of this ability.
	 */
	private double cost = 0d;
	
	/**
	 * The resource name for the cost.
	 */
	private String resource = null;
	
	/**
	 * The remaining CoolDown, if the ability was executed.
	 */
	private double remainingCooldownMs = 0d;
	
	/**
	 * Base damage modifier, which can vary for the min and max values 
	 * of the ability.
	 */
	private double standardHealthPercentMax = 0d;
	
	/**
	 * Base damage modifier, which can vary for the min and max values 
	 * of the ability.
	 */
	private double standardHealthPercentMin = 0d;
	
	/**
	 * Weapon damage modifier for min and max values and all ranks of the 
	 * ability.
	 */
	private double amountModifierFixedMax = 0d;
	
	/**
	 * Weapon damage modifier for min and max values and all ranks of the 
	 * ability.
	 */
	private double amountModifierFixedMin = 0d;
	
	/**
	 * If the other two are '0' i think this modifier is used.
	 */
	private double amountModifierPercent = 0d;
	
	/**
	 * Force/Tech/Melee/Ranged damage bonus modifier for min and max values 
	 * and all ranks of the ability.
	 */
	private double coefficient = 0d;
	
	/**
	 * The level at which is the HIGHEST rank of the ability is trainable.
	 * The standardDamage of trained abilities is looked up on the damage 
	 * per level table using level it's trainable.
	 * The standardDamage of the tooltip values in the skill trees are 
	 * based on the damage per level table using the level 5+5*Tier 
	 * (i.e. 40 for a top tier attack).
	 */
	private int trainableLevel = 0;
	
	/**
	 * The interval of the periodic effect.
	 */
	private double periodicInterval = 0d;
	
	/**
	 * Calculations are based on character stats. Those change while simulating.
	 * So every ability needs a reference to the changing object.
	 */
	private ACharacterStats charStats = null;
	
	/* ********************************* MISC *********************************/
	
	/**
	 * Set the amount modifier. <b>IMPORTANT:</b> "+ 1" is added, so do not 
	 * add it before. Just add the modifier.
	 * @param d Amount modifier.
	 */
	public final void setAmountModifierPercent(final double d) {
		this.amountModifierPercent = d + 1d;
	}
	
	/**
	 * The average direct damage or direct healing.
	 * See <a>http://tinyurl.com/6feflpr</a>.
	 * @return The average direct damage or direct healing.
	 * @throws DroidMathException Invalid damage values.
	 */
	public final double averageDirect() throws DroidMathException {
		final double minDirect = this.getMinDirect();
		final double maxDirect = this.getMaxDirect();
		
		if (minDirect > maxDirect) {
			throw new DroidMathException("minDirect > maxDirect", 
					this.getClass());
		}
		if (minDirect < 0) {
			throw new DroidMathException("minDirect < 0", 
					this.getClass());
		}
		if (maxDirect < 0) {
			throw new DroidMathException("maxDirect < 0", 
					this.getClass());
		}
		return (minDirect + maxDirect) / 2d;
	}
	
	/**
	 * The average "over time" damage or "over time" healing.
	 * See <a>http://tinyurl.com/6feflpr</a>.
	 * @return The average "over time" damage or "over time" healing.
	 * @throws DroidMathException Invalid damage values.
	 */
	public final double averagePeriodic() throws DroidMathException {
		final double minPeriodic = this.getMinPeriodic();
		final double maxPeriodic = this.getMaxPeriodic();
		
		if (minPeriodic > maxPeriodic) {
			throw new DroidMathException("minPeriodic > maxPeriodic", 
					this.getClass());
		}
		if (minPeriodic < 0) {
			throw new DroidMathException("minPeriodic < 0", 
					this.getClass());
		}
		if (maxPeriodic < 0) {
			throw new DroidMathException("maxPeriodic < 0", 
					this.getClass());
		}
		return (minPeriodic + maxPeriodic) / 2d;
	}
	
	/**
	 * Check if this ability is available. Some abilities require a talent.
	 * @return If this ability is available.
	 */
	public final boolean isAvailabe() {
		boolean b = true;
		
		for (HashMap<String, Talent> tree 
				: Model.getProfile().getSpecTemplate().values()) {
			if (tree.containsKey(this.requiredTalent)) {
				if (tree.get(this.requiredTalent).getPoints() == 0) {
					b = false;
				}
			}
		}
		
		return b;
	}
	
	@Override
	public final Ability clone() {
		Ability a = null;
		
		try {
			a = (Ability) super.clone();
			
		} catch (CloneNotSupportedException e) {
			ExceptionHandler.report("Could not clone ability.", e);
		}
		
		return a;
	}
	
	/**
	 * Get the standard damage for the ability's level.
	 * @return Standard damage.
	 */
	public final int getStdDmg() {	
		return this.stdDmg.get(this.trainableLevel);
	}
	
	/**
	 * Get the description. You can override this method.
	 * @return the description
	 */
	public String getDescription() {
		return description;
	}
	
	/* ************************ DAMAGE/HEALING CALCULATIONS *******************/
	
	/**
	 * Get the minimum damage dealt over time.
	 * @return Minimum damage dealt over time.
	 */
	public abstract double getMinPeriodic();
	
	/**
	 * Get the maximum damage dealt over time.
	 * @return Maximum damage dealt over time.
	 */
	public abstract double getMaxPeriodic();
	
	/**
	 * Get the minimum direct damage.
	 * @return Minimum direct damage.
	 */
	public abstract double getMinDirect();
	
	/**
	 * Get the maximum direct damage.
	 * @return Maximum direct damage.
	 */
	public abstract double getMaxDirect();
	
	/* ***************************** GETTERS / SETTERS ************************/
	
	/**
	 * Get the duration of the damage over time effect in milliseconds.
	 * @return Duration of damage over time effect.
	 */
	public final double getDurationMs() {
		return durationMs;
	}
	
	/**
	 * Set the duration of the damage over time effect in milliseconds.
	 * @param duration Duration of the damage over time effect in milliseconds.
	 */
	public final void setDurationMs(final double duration) {
		this.durationMs = duration;
	}
	
	/**
	 * Get the cooldown in milliseconds.
	 * @return Cooldown in milliseconds.
	 */
	public final double getCooldownMs() {
		return cooldownMs;
	}
	
	/**
	 * Set the cooldown in milliseconds.
	 * @param cooldown Cooldown in milliseconds.
	 */
	public final void setCooldownMs(final double cooldown) {
		this.cooldownMs = cooldown;
	}

	/**
	 * @return the name
	 */
	public final String getName() {
		return name;
	}

	/**
	 * @param n the name to set
	 */
	public final void setName(final String n) {
		this.name = n;
	}

	/**
	 * @return the iconName
	 */
	public final String getIconName() {
		return iconName;
	}

	/**
	 * @param n the iconName to set
	 */
	public final void setIconName(final String n) {
		this.iconName = n;
	}

	/**
	 * @return the enabled
	 */
	public final boolean isEnabled() {
		return enabled;
	}

	/**
	 * @param e the enabled to set
	 */
	public final void setEnabled(final boolean e) {
		this.enabled = e;
	}

	/**
	 * @return the affectedByGCD
	 */
	public final boolean isAffectedByGCD() {
		return affectedByGCD;
	}

	/**
	 * @param b the affectedByGCD to set
	 */
	public final void setAffectedByGCD(final boolean b) {
		this.affectedByGCD = b;
	}

	/**
	 * @return the cost
	 */
	public final double getCost() {
		return cost;
	}

	/**
	 * @param c the cost to set
	 */
	public final void setCost(final double c) {
		this.cost = c;
	}

	/**
	 * @return the remainingCooldown
	 */
	public final double getRemainingCooldownMs() {
		return remainingCooldownMs;
	}

	/**
	 * @param r the remainingCooldown to set
	 */
	public final void setRemainingCooldownMs(final double r) {
		this.remainingCooldownMs = r;
	}

	/**
	 * @param a the affectsGCD to set
	 */
	public final void setAffectsGCD(final boolean a) {
		this.affectsGCD = a;
	}

	/**
	 * @return the affectsGCD
	 */
	public final boolean affectsGCD() {
		return affectsGCD;
	}

	/**
	 * @return the directDamageType
	 */
	public final EDmgType getDirectDamageType() {
		return directDamageType;
	}

	/**
	 * @param p the periodicDamageType to set
	 */
	public final void setPeriodicDamageType(final EDmgType p) {
		this.periodicDamageType = p;
	}

	/**
	 * @return the periodicDamageType
	 */
	public final EDmgType getPeriodicDamageType() {
		return periodicDamageType;
	}

	/**
	 * @param d the description to set
	 */
	public final void setDescription(final String d) {
		this.description = d;
	}

	/**
	 * @param r the resource to set
	 */
	public final void setResource(final String r) {
		this.resource = r;
	}

	/**
	 * @return the resource
	 */
	public final String getResource() {
		return resource;
	}

	/**
	 * @param r the requiredTalent to set
	 */
	public final void setRequiredTalent(final String r) {
		this.requiredTalent = r;
	}

	/**
	 * @return the requiredTalent
	 */
	public final String getRequiredTalent() {
		return requiredTalent;
	}

	/**
	 * @param t the castTimeMS to set
	 */
	public final void setCastTimeMS(final double t) {
		this.castTimeMs = t;
	}

	/**
	 * @return the castTimeMs
	 */
	public final double getCastTimeMs() {
		return castTimeMs;
	}

	/**
	 * @param b the usedForOptimization to set
	 */
	public final void setUsedForOptimization(final boolean b) {
		this.usedForOptimization = b;
	}

	/**
	 * @return the usedForOptimization
	 */
	public final boolean isUsedForOptimization() {
		return usedForOptimization;
	}

	/**
	 * @param d the standardHealthPercentMax to set
	 */
	public final void setStandardHealthPercentMax(final double d) {
		this.standardHealthPercentMax = d;
	}

	/**
	 * @return the standardHealthPercentMax
	 */
	public final double getStandardHealthPercentMax() {
		return standardHealthPercentMax;
	}

	/**
	 * @param d the standardHealthPercentMin to set
	 */
	public final void setStandardHealthPercentMin(final double d) {
		this.standardHealthPercentMin = d;
	}

	/**
	 * @return the standardHealthPercentMin
	 */
	public final double getStandardHealthPercentMin() {
		return standardHealthPercentMin;
	}

	/**
	 * @param d the amountModifierFixedMax to set
	 */
	public final void setAmountModifierFixedMax(final double d) {
		this.amountModifierFixedMax = d;
	}

	/**
	 * @return the amountModifierFixedMax
	 */
	public final double getAmountModifierFixedMax() {
		return amountModifierFixedMax;
	}

	/**
	 * @param d the amountModifierFixedMin to set
	 */
	public final void setAmountModifierFixedMin(final double d) {
		this.amountModifierFixedMin = d;
	}

	/**
	 * @return the amountModifierFixedMin
	 */
	public final double getAmountModifierFixedMin() {
		return amountModifierFixedMin;
	}

	/**
	 * @return the amountModifierPercent
	 */
	public final double getAmountModifierPercent() {
		return amountModifierPercent;
	}

	/**
	 * @param c the coefficient to set
	 */
	public final void setCoefficient(final double c) {
		this.coefficient = c;
	}

	/**
	 * @return the coefficient
	 */
	public final double getCoefficient() {
		return coefficient;
	}

	/**
	 * @param l the trainableLevel to set
	 */
	public final void setTrainableLevel(final int l) {
		this.trainableLevel = l;
	}

	/**
	 * @return the trainableLevel
	 */
	public final int getTrainableLevel() {
		return trainableLevel;
	}

	/**
	 * @param d the periodicInterval to set
	 */
	public final void setPeriodicInterval(final double d) {
		this.periodicInterval = d;
	}

	/**
	 * @return the periodicInterval
	 */
	public final double getPeriodicInterval() {
		return periodicInterval;
	}
	
	/**
	 * @param d Set the direct damage type.
	 */
	public final void setDirectDamageType(final EDmgType d) {
		this.directDamageType = d;
	}

	/**
	 * @param c the charStats to set
	 */
	public final void setCharStats(final ACharacterStats c) {
		this.charStats = c;
	}

	/**
	 * @return the charStats
	 */
	public final ACharacterStats getCharStats() {
		return charStats;
	}
}
